<script>
    // SqlServer 数据库死锁
    
            /*
                一、什么是死锁？

                            死锁是指两个或更多个进程在执行过程中，因争夺资源而造成的一种互相等待的现象。若无外力干涉，这些进程都将无法向前推进。

                            ★、一个经典的比喻：  
                                                独木桥上的两个人

                                            两个人分别从独木桥的两端走向对面。他们在桥中间相遇，谁也不愿意后退，都在等待对方先让路。

                                            如果没有第三方(比如交警)来协调介入，他们将永远僵持下去。

                            在SQL Server 中，这两个 "人" 就是两个事务， "路"就是被锁定的数据库资源 (如 行、页、表)
            */

            /*
                二、死锁产生的四个必要条件：

                        1、互斥条件:    一个资源每次只能被一个进程使用。

                        2、请求与保持条件：  一个进程因请求资源而阻塞时，此时对其已经获取的资源保持不放。

                        3、不剥夺条件：   进程已经获得的资源，在未使用完之前，不能被强行剥夺。

                        4、循环等待条件：  若干京城之间形成一种头尾相接的循环等待资源关系。
            */

            /*
                三、SQL Server 中常见的死锁具体原因

                        1、不同顺序的访问对象  (最常见)

                                场景：   事务A 先锁表X，再请求锁表Y；  同时，事务B先锁表Y，再请求锁表X。

                                原因：  访问相同一组对象，但顺序不一致，极易形成循环等待

                        2、缺失或不当的索引

                                场景：  一个事务执行全表扫描或范围扫描，锁定了大量行 (甚至升级为表锁)，另一个事事务恰好要更新其中某一行，或者请求一个兼容锁，导致阻塞和死锁。

                                原因：  没有合适的索引导致锁的颗粒度变大，增加了冲突的概率。

                        3、长时间运行的事务

                                场景：  一个事务长时间保持其锁 (例如，在事务中进行大量操作或等待用户输入)，其他事物不得不长时间等待，
                                
                                        在这期间，其他事务可能持有了这个等待事务所需要的资源，从而导致死锁。

                                原因：  锁持有的时间过长，增大了与其他事务放生冲突的时间窗口。

                        4、锁升级

                                场景：  SQL Server 为了节省内存，可能会将大量细颗粒度的 "行锁" 或 "页锁" 升级为 "表锁"。

                                        如果另一个事务已经持有了该表的某些行锁，或者正在请求该表的锁，就可能发生死锁。

                                原因：  锁颗粒度突然变大，意外地阻塞了更多请求。

                        5、读写并发与更新锁 (U锁)

                                场景：  
                                        <1>、事务 A 持有一个共享锁（S锁）读取某行。
                                    
                                        <2>、事务 B 也想更新该行，它请求一个排他锁（X锁）。由于 X锁与 S锁 不兼容，事务 B 被阻塞，进入等待队列。

                                        <3>、此时，如果事务 A 想要升级它的 S锁 为 X锁 以进行更新，它必须等待事务 B 释放其锁请求（尽管事务 B 是被阻塞的）。

                                                这就形成了循环等待：A 在等 B，B 在等 A。

                                原因：  SQL Server 使用更新锁（U锁） 来预防这种常见情况。U锁 与 S锁 兼容，但与另一个 U锁 或 X锁 不兼容。、

                                        事务在修改数据前会先申请 U锁，这样可以避免上述的转换死锁。
            */

            /*
                四、如何处理和解决死锁？

                         死锁无法完全解决，但可以通过一系列措施来最大限度地减少其发生频率和影响。

                         1、即时处理：SQL Server 自动处理

                                        SQL Server 有一个内置的 "死锁监视器" 线程，它定期检查系统是否有死锁.
                                        
                                        一旦检测到死锁，它会选择一个代价最小的事务作为 "牺牲品"，将其回滚并向应用程序抛出 "1205" 错误，从而让另一个事物可以继续运行。

                                        作为开发者，你的应用程序 "必须能够捕获并处理 1205错误"，通常的策略是：

                                                ①、记录错误信息

                                                ②、等待一小段时间

                                                ③、自动重试整个事务 (通常最多重试3次)

                         2、调查与分析： 捕获死锁信息

                                        要解决问题，首先要知道死锁是如何发生的。

                                        <1>、开启跟踪标志 (传统方法)：

                                                sql语句：

                                                        DBCC TRACEON(1222,-1)DBCC TRACEON (1222, -1) -- 将死锁信息输出到错误日志
                                                        DBCC TRACEON (1204, -1) -- 提供更详细的死锁信息

                                        <2>、使用Sql Server Profile (SSMS工具)：

                                                创建一个跟踪，捕获 Deadlock graph 事件。

                                                它会提供一个图形化界面，清晰展示哪些事务参与了死锁、它们争夺的资源是什么以及谁被选择了牺牲品。

                                        <3>、使用扩展事件 (Extended Events,推荐方法)

                                                大的现代方法。可以创建一个扩展事件会话来捕获 xml_deadlock_report 事件，将死锁信息保存到文件或表中，便于后续分析。


                         3、根本性预防与优化

                                        <1>、保持一致的访问顺序：

                                                在应用程序代码中，规定所有事务对多个对象的访问都按照相同的顺序（例如，先表A，再表B）。这可以打破“循环等待”条件。

                                        <2>、创建有效的索引：

                                                分析死锁图形和执行计划，检查是否因为全表扫描导致锁升级。添加合适的索引可以缩小锁定范围，减少锁冲突。

                                        <3>、缩短事务长度：

                                                ①、事务应尽可能短小精悍。

                                                ②、不要在事务中执行用户交互（如等待用户确认），这会使锁保持很长时间。

                                                ③、将不必要的 SELECT 语句移到事务外，只在事务中包含最必要的 INSERT/UPDATE/DELETE 操作。

                                        <4>、使用较低的隔离级别：

                                                默认的 READ COMMITTED 隔离级别在读取后就会释放共享锁。、

                                                如果业务允许，可以考虑使用 READ COMMITTED SNAPSHOT 或 SNAPSHOT ISOLATION，它们通过行版本控制来实现读一致性，
                                                
                                                读者不会阻塞写者，写者也不会阻塞读者，从而从根本上减少很多死锁场景。

                                        <5>、使用提示（Hints）精细化控制锁行为（谨慎使用）

                                                ①、ROWLOCK：强制使用行级锁，避免锁升级。

                                                ②、UPDLOCK：在读取时使用更新锁（U锁），提示数据库你即将要更新该行，防止其他事务获取该行的U锁或X锁。

                                                ③、NOLOCK：脏读，仅用于非常不重要的统计场景，风险极高，一般不推荐。

                                                ④、READPAST：跳过已被锁定的行，适用于高并发队列处理。

                                                注意： 锁提示是一把双刃剑，使用不当可能会引发其他问题或降低并发性。
            */

            /*
                总结
                
                     阶段	    核心方法

                    即时响应	应用程序捕获 1205 错误并实现重试机制。

                    调查分析	使用扩展事件或 Profiler 捕获 Deadlock Graph，定位根源。

                    预防优化	
                            1. 顺序访问
                            2. 优化索引
                            3. 缩短事务
                            4. 降低隔离级别（使用行版本控制）
                            5. 谨慎使用锁提示

                    处理死锁是一个系统性的工程，需要结合监控、分析和代码优化等多种手段。最重要的第一步是设置好死锁信息的捕获机制，否则一切优化都是盲人摸象。

                    处理死锁是一个系统性的工程，需要结合监控、分析和代码优化等多种手段。最重要的第一步是设置好死锁信息的捕获机制，否则一切优化都是盲人摸象。
            */
</script>